// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/tools/flip_server/spdy_interface.h"

#include <list>
#include <memory>

#include "base/strings/string_piece.h"
#include "net/spdy/buffered_spdy_framer.h"
#include "net/tools/balsa/balsa_enums.h"
#include "net/tools/balsa/balsa_headers.h"
#include "net/tools/flip_server/flip_config.h"
#include "net/tools/flip_server/flip_test_utils.h"
#include "net/tools/flip_server/mem_cache.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

using ::base::StringPiece;
using ::testing::_;
using ::testing::InSequence;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Values;

inline bool operator==(StringPiece x,
    const SpdyHeaderBlock::StringPieceProxy& y)
{
    return x == y.operator StringPiece();
}

namespace {

    struct StringSaver {
    public:
        StringSaver()
            : data(NULL)
            , size(0)
        {
        }
        void Save() { string = std::string(data, size); }

        const char* data;
        size_t size;
        std::string string;
    };

    class SpdyFramerVisitor : public BufferedSpdyFramerVisitorInterface {
    public:
        virtual ~SpdyFramerVisitor() { }
        MOCK_METHOD1(OnError, void(SpdyFramer::SpdyError));
        MOCK_METHOD2(OnStreamError, void(SpdyStreamId, const std::string&));
        // SaveArg cannot be used on non-copyable types like SpdyHeaderBlock.
        void OnSynStream(SpdyStreamId stream_id,
            SpdyStreamId associated_stream_id,
            SpdyPriority priority,
            bool fin,
            bool unidirectional,
            const SpdyHeaderBlock& headers) override
        {
            actual_header_block_ = headers.Clone();
            OnSynStreamMock(stream_id, associated_stream_id, priority, fin,
                unidirectional, headers);
        }
        MOCK_METHOD6(OnSynStreamMock,
            void(SpdyStreamId,
                SpdyStreamId,
                SpdyPriority,
                bool,
                bool,
                const SpdyHeaderBlock&));
        void OnSynReply(SpdyStreamId stream_id,
            bool fin,
            const SpdyHeaderBlock& headers) override
        {
            actual_header_block_ = headers.Clone();
            OnSynReplyMock(stream_id, fin, headers);
        }
        MOCK_METHOD3(OnSynReplyMock,
            void(SpdyStreamId, bool, const SpdyHeaderBlock&));
        void OnHeaders(SpdyStreamId stream_id,
            bool has_priority,
            int weight,
            SpdyStreamId parent_stream_id,
            bool exclusive,
            bool fin,
            const SpdyHeaderBlock& headers) override
        {
            actual_header_block_ = headers.Clone();
            OnHeadersMock(stream_id, has_priority, weight, parent_stream_id, exclusive,
                fin, headers);
        }
        MOCK_METHOD7(OnHeadersMock,
            void(SpdyStreamId stream_id,
                bool has_priority,
                int weight,
                SpdyStreamId parent_stream_id,
                bool exclusive,
                bool fin,
                const SpdyHeaderBlock& headers));
        MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId, size_t, bool));
        MOCK_METHOD3(OnStreamFrameData, void(SpdyStreamId, const char*, size_t));
        MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId));
        MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId, size_t));
        MOCK_METHOD1(OnHeaderFrameStart,
            SpdyHeadersHandlerInterface*(SpdyStreamId stream_id));
        MOCK_METHOD2(OnHeaderFrameEnd,
            void(SpdyStreamId stream_id, bool end_headers));
        MOCK_METHOD1(OnSettings, void(bool clear_persisted));
        MOCK_METHOD3(OnSetting, void(SpdySettingsIds, uint8_t, uint32_t));
        MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
        MOCK_METHOD2(OnRstStream, void(SpdyStreamId, SpdyRstStreamStatus));
        MOCK_METHOD3(OnGoAway, void(SpdyStreamId, SpdyGoAwayStatus, StringPiece));
        MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId, int));
        MOCK_METHOD3(OnPushPromise,
            void(SpdyStreamId, SpdyStreamId, const SpdyHeaderBlock&));
        MOCK_METHOD3(OnAltSvc,
            void(SpdyStreamId,
                base::StringPiece,
                const SpdyAltSvcWireFormat::AlternativeServiceVector&));
        MOCK_METHOD2(OnUnknownFrame, bool(SpdyStreamId stream_id, int frame_type));

        SpdyHeaderBlock actual_header_block_;
    };

    class FakeSMConnection : public SMConnection {
    public:
        FakeSMConnection(EpollServer* epoll_server,
            SSLState* ssl_state,
            MemoryCache* memory_cache,
            FlipAcceptor* acceptor,
            std::string log_prefix)
            : SMConnection(epoll_server,
                ssl_state,
                memory_cache,
                acceptor,
                log_prefix)
        {
        }

        MOCK_METHOD0(Cleanup, void());
        MOCK_METHOD8(InitSMConnection,
            void(SMConnectionPoolInterface*,
                SMInterface*,
                EpollServer*,
                int,
                std::string,
                std::string,
                std::string,
                bool));
    };

    // This class is almost SpdySM, except one function.
    // This class is the test target of tests in this file.
    class TestSpdySM : public SpdySM {
    public:
        virtual ~TestSpdySM() { }
        TestSpdySM(SMConnection* connection,
            SMInterface* sm_http_interface,
            EpollServer* epoll_server,
            MemoryCache* memory_cache,
            FlipAcceptor* acceptor,
            SpdyMajorVersion version)
            : SpdySM(connection,
                sm_http_interface,
                epoll_server,
                memory_cache,
                acceptor,
                version)
        {
        }

        MOCK_METHOD2(FindOrMakeNewSMConnectionInterface,
            SMInterface*(const std::string&, const std::string&));
    };

    class SpdySMTestBase : public ::testing::TestWithParam<SpdyMajorVersion> {
    public:
        explicit SpdySMTestBase(FlipHandlerType type)
        {
            SSLState* ssl_state = NULL;
            mock_another_interface_.reset(new MockSMInterface);
            memory_cache_.reset(new MemoryCache);
            acceptor_.reset(new FlipAcceptor(type,
                "127.0.0.1",
                "8941",
                "ssl_cert_filename",
                "ssl_key_filename",
                "127.0.0.1",
                "8942",
                "127.0.0.1",
                "8943",
                1,
                0,
                true,
                1,
                false,
                true,
                NULL));
            epoll_server_.reset(new EpollServer);
            connection_.reset(new FakeSMConnection(epoll_server_.get(),
                ssl_state,
                memory_cache_.get(),
                acceptor_.get(),
                "log_prefix"));

            interface_.reset(new TestSpdySM(connection_.get(),
                mock_another_interface_.get(),
                epoll_server_.get(),
                memory_cache_.get(),
                acceptor_.get(),
                GetParam()));

            spdy_framer_.reset(new BufferedSpdyFramer(GetParam()));
            spdy_framer_visitor_.reset(new SpdyFramerVisitor);
            spdy_framer_->set_visitor(spdy_framer_visitor_.get());
        }

        virtual ~SpdySMTestBase()
        {
            if (acceptor_->listen_fd_ >= 0) {
                epoll_server_->UnregisterFD(acceptor_->listen_fd_);
                close(acceptor_->listen_fd_);
                acceptor_->listen_fd_ = -1;
            }
            OutputList& output_list = *connection_->output_list();
            for (OutputList::const_iterator i = output_list.begin();
                 i != output_list.end();
                 ++i) {
                delete *i;
            }
            output_list.clear();
        }

        bool HasStream(uint32_t stream_id)
        {
            return interface_->output_ordering().ExistsInPriorityMaps(stream_id);
        }

    protected:
        std::unique_ptr<MockSMInterface> mock_another_interface_;
        std::unique_ptr<MemoryCache> memory_cache_;
        std::unique_ptr<FlipAcceptor> acceptor_;
        std::unique_ptr<EpollServer> epoll_server_;
        std::unique_ptr<FakeSMConnection> connection_;
        std::unique_ptr<TestSpdySM> interface_;
        std::unique_ptr<BufferedSpdyFramer> spdy_framer_;
        std::unique_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
    };

    class SpdySMProxyTest : public SpdySMTestBase {
    public:
        SpdySMProxyTest()
            : SpdySMTestBase(FLIP_HANDLER_PROXY)
        {
        }
        virtual ~SpdySMProxyTest() { }
    };

    class SpdySMServerTest : public SpdySMTestBase {
    public:
        SpdySMServerTest()
            : SpdySMTestBase(FLIP_HANDLER_SPDY_SERVER)
        {
        }
        virtual ~SpdySMServerTest() { }
    };

    INSTANTIATE_TEST_CASE_P(SpdySMProxyTest, SpdySMProxyTest, Values(SPDY3, HTTP2));
    INSTANTIATE_TEST_CASE_P(SpdySMServerTest, SpdySMServerTest, Values(HTTP2));

    TEST_P(SpdySMProxyTest, InitSMConnection)
    {
        {
            InSequence s;
            EXPECT_CALL(*connection_, InitSMConnection(_, _, _, _, _, _, _, _));
        }
        interface_->InitSMConnection(
            NULL, NULL, epoll_server_.get(), -1, "", "", "", false);
    }

    TEST_P(SpdySMProxyTest, OnStreamFrameData)
    {
        BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
        std::unique_ptr<MockSMInterface> mock_interface(new MockSMInterface);
        uint32_t stream_id = 92;
        uint32_t associated_id = 43;
        SpdyHeaderBlock block;
        testing::MockFunction<void(int)> checkpoint; // NOLINT

        std::unique_ptr<SpdySerializedFrame> frame(
            spdy_framer_->CreatePingFrame(12, false));
        block[":method"] = "GET";
        block[":host"] = "www.example.com";
        block[":path"] = "/path";
        block[":scheme"] = "http";
        block["foo"] = "bar";
        {
            InSequence s;
            EXPECT_CALL(*interface_,
                FindOrMakeNewSMConnectionInterface(_, _))
                .WillOnce(Return(mock_interface.get()));
            EXPECT_CALL(*mock_interface, SetStreamID(stream_id));
            EXPECT_CALL(*mock_interface, ProcessWriteInput(_, _)).Times(1);
            EXPECT_CALL(checkpoint, Call(0));
            EXPECT_CALL(*mock_interface,
                ProcessWriteInput(frame->data(), frame->size()))
                .Times(1);
        }

        visitor->OnSynStream(stream_id, associated_id, 0, false, false, block);
        checkpoint.Call(0);
        visitor->OnStreamFrameData(stream_id, frame->data(), frame->size());
    }

    TEST_P(SpdySMProxyTest, OnRstStream)
    {
        BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
        uint32_t stream_id = 82;
        MemCacheIter mci;
        mci.stream_id = stream_id;

        {
            BalsaHeaders headers;
            std::string filename = "foobar";
            memory_cache_->InsertFile(&headers, filename, "");
            mci.file_data = memory_cache_->GetFileData(filename);
        }

        interface_->AddToOutputOrder(mci);
        ASSERT_TRUE(HasStream(stream_id));
        visitor->OnRstStream(stream_id, RST_STREAM_INVALID);
        ASSERT_FALSE(HasStream(stream_id));
    }

    TEST_P(SpdySMProxyTest, ProcessReadInput)
    {
        ASSERT_EQ(SpdyFramer::SPDY_READY_FOR_FRAME,
            interface_->spdy_framer()->state());
        interface_->ProcessReadInput("", 1);
        ASSERT_EQ(SpdyFramer::SPDY_READING_COMMON_HEADER,
            interface_->spdy_framer()->state());
    }

    TEST_P(SpdySMProxyTest, ResetForNewConnection)
    {
        uint32_t stream_id = 13;
        MemCacheIter mci;
        mci.stream_id = stream_id;
        // incomplete input
        const char input[] = { '\0', '\0', '\0' };

        {
            BalsaHeaders headers;
            std::string filename = "foobar";
            memory_cache_->InsertFile(&headers, filename, "");
            mci.file_data = memory_cache_->GetFileData(filename);
        }

        interface_->AddToOutputOrder(mci);
        ASSERT_TRUE(HasStream(stream_id));
        interface_->ProcessReadInput(input, sizeof(input));
        ASSERT_NE(SpdyFramer::SPDY_READY_FOR_FRAME,
            interface_->spdy_framer()->state());

        interface_->ResetForNewConnection();
        ASSERT_FALSE(HasStream(stream_id));
        ASSERT_TRUE(interface_->spdy_framer() == NULL);
    }

    TEST_P(SpdySMProxyTest, CreateFramer)
    {
        interface_->ResetForNewConnection();
        interface_->CreateFramer(SPDY3);
        ASSERT_TRUE(interface_->spdy_framer() != NULL);
        ASSERT_EQ(interface_->spdy_version(), SPDY3);

        interface_->ResetForNewConnection();
        interface_->CreateFramer(HTTP2);
        ASSERT_TRUE(interface_->spdy_framer() != NULL);
        ASSERT_EQ(interface_->spdy_version(), HTTP2);
    }

    TEST_P(SpdySMProxyTest, PostAcceptHook)
    {
        interface_->PostAcceptHook();

        ASSERT_EQ(1u, connection_->output_list()->size());
        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;

        {
            InSequence s;
            EXPECT_CALL(*spdy_framer_visitor_, OnSettings(false));
            EXPECT_CALL(*spdy_framer_visitor_,
                OnSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0u, 100u));
        }
        spdy_framer_->ProcessInput(df->data, df->size);
    }

    TEST_P(SpdySMProxyTest, NewStream)
    {
        // TODO(yhirano): SpdySM::NewStream leads to crash when
        // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
        // It should be fixed though I don't know the solution now.
    }

    TEST_P(SpdySMProxyTest, AddToOutputOrder)
    {
        uint32_t stream_id = 13;
        MemCacheIter mci;
        mci.stream_id = stream_id;

        {
            BalsaHeaders headers;
            std::string filename = "foobar";
            memory_cache_->InsertFile(&headers, filename, "");
            mci.file_data = memory_cache_->GetFileData(filename);
        }

        interface_->AddToOutputOrder(mci);
        ASSERT_TRUE(HasStream(stream_id));
    }

    TEST_P(SpdySMProxyTest, SendErrorNotFound)
    {
        uint32_t stream_id = 82;
        const char* actual_data;
        size_t actual_size;
        testing::MockFunction<void(int)> checkpoint; // NOLINT

        interface_->SendErrorNotFound(stream_id);

        ASSERT_EQ(2u, connection_->output_list()->size());

        {
            InSequence s;
            if (GetParam() < HTTP2) {
                EXPECT_CALL(*spdy_framer_visitor_, OnSynReplyMock(stream_id, false, _));
            } else {
                EXPECT_CALL(*spdy_framer_visitor_,
                    OnHeadersMock(stream_id, /*has_priority=*/false, _, _, _,
                        /*fin=*/false, _));
            }
            EXPECT_CALL(checkpoint, Call(0));
            EXPECT_CALL(*spdy_framer_visitor_,
                OnDataFrameHeader(stream_id, _, true));
            EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, _, _))
                .Times(1)
                .WillOnce(DoAll(SaveArg<1>(&actual_data), SaveArg<2>(&actual_size)));
            EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, NULL, 0))
                .Times(1);
        }

        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);
        checkpoint.Call(0);
        df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);

        ASSERT_EQ(2, spdy_framer_->frames_received());
        ASSERT_EQ(2u, spdy_framer_visitor_->actual_header_block_.size());
        ASSERT_EQ("404 Not Found",
            spdy_framer_visitor_->actual_header_block_[":status"]);
        ASSERT_EQ("HTTP/1.1", spdy_framer_visitor_->actual_header_block_[":version"]);
        ASSERT_EQ("wtf?", StringPiece(actual_data, actual_size));
    }

    TEST_P(SpdySMProxyTest, SendSynStream)
    {
        uint32_t stream_id = 82;
        BalsaHeaders headers;
        headers.AppendHeader("key1", "value1");
        headers.AppendHeader("Host", "www.example.com");
        headers.SetRequestFirstlineFromStringPieces("GET", "/path", "HTTP/1.1");

        interface_->SendSynStream(stream_id, headers);

        ASSERT_EQ(1u, connection_->output_list()->size());
        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;

        {
            InSequence s;
            EXPECT_CALL(*spdy_framer_visitor_,
                OnSynStreamMock(stream_id, 0, _, false, false, _));
        }

        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ(1, spdy_framer_->frames_received());
        ASSERT_EQ(5u, spdy_framer_visitor_->actual_header_block_.size());
        ASSERT_EQ("GET", spdy_framer_visitor_->actual_header_block_[":method"]);
        ASSERT_EQ("HTTP/1.1", spdy_framer_visitor_->actual_header_block_[":version"]);
        ASSERT_EQ("/path", spdy_framer_visitor_->actual_header_block_[":path"]);
        ASSERT_EQ("www.example.com",
            spdy_framer_visitor_->actual_header_block_[":host"]);
        ASSERT_EQ("value1", spdy_framer_visitor_->actual_header_block_["key1"]);
    }

    TEST_P(SpdySMProxyTest, SendSynReply)
    {
        uint32_t stream_id = 82;
        BalsaHeaders headers;
        headers.AppendHeader("key1", "value1");
        headers.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "OK");

        interface_->SendSynReply(stream_id, headers);

        ASSERT_EQ(1u, connection_->output_list()->size());
        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;

        {
            InSequence s;
            if (GetParam() < HTTP2) {
                EXPECT_CALL(*spdy_framer_visitor_, OnSynReplyMock(stream_id, false, _));
            } else {
                EXPECT_CALL(*spdy_framer_visitor_,
                    OnHeadersMock(stream_id, /*has_priority=*/false, _, _, _,
                        /*fin=*/false, _));
            }
        }

        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ(1, spdy_framer_->frames_received());
        ASSERT_EQ(3u, spdy_framer_visitor_->actual_header_block_.size());
        ASSERT_EQ("200 OK", spdy_framer_visitor_->actual_header_block_[":status"]);
        ASSERT_EQ("HTTP/1.1", spdy_framer_visitor_->actual_header_block_[":version"]);
        ASSERT_EQ("value1", spdy_framer_visitor_->actual_header_block_["key1"]);
    }

    TEST_P(SpdySMProxyTest, SendDataFrame)
    {
        uint32_t stream_id = 133;
        SpdyDataFlags flags = DATA_FLAG_NONE;
        const char* actual_data;
        size_t actual_size;

        interface_->SendDataFrame(stream_id, "hello", 5, flags, true);

        ASSERT_EQ(1u, connection_->output_list()->size());
        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;

        {
            InSequence s;
            EXPECT_CALL(*spdy_framer_visitor_,
                OnDataFrameHeader(stream_id, _, false));
            EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, _, _))
                .WillOnce(DoAll(SaveArg<1>(&actual_data), SaveArg<2>(&actual_size)));
        }

        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ(1, spdy_framer_->frames_received());
        ASSERT_EQ("hello", StringPiece(actual_data, actual_size));
    }

    TEST_P(SpdySMProxyTest, SendLongDataFrame)
    {
        uint32_t stream_id = 133;
        SpdyDataFlags flags = DATA_FLAG_NONE;
        const char* actual_data;
        size_t actual_size;

        std::string data = std::string(kSpdySegmentSize, 'a') + std::string(kSpdySegmentSize, 'b') + "c";
        interface_->SendDataFrame(stream_id, data.data(), data.size(), flags, true);

        {
            InSequence s;
            for (int i = 0; i < 3; ++i) {
                EXPECT_CALL(*spdy_framer_visitor_,
                    OnDataFrameHeader(stream_id, _, false));
                EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, _, _))
                    .WillOnce(
                        DoAll(SaveArg<1>(&actual_data), SaveArg<2>(&actual_size)));
            }
        }

        ASSERT_EQ(3u, connection_->output_list()->size());
        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ(std::string(kSpdySegmentSize, 'a'),
            StringPiece(actual_data, actual_size));

        df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ(std::string(kSpdySegmentSize, 'b'),
            StringPiece(actual_data, actual_size));

        df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);
        ASSERT_EQ("c", StringPiece(actual_data, actual_size));
    }

    TEST_P(SpdySMServerTest, OnSynStream)
    {
        BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
        uint32_t stream_id = 82;
        SpdyHeaderBlock spdy_headers;
        spdy_headers["url"] = "http://www.example.com/path";
        spdy_headers["method"] = "GET";
        spdy_headers["scheme"] = "http";
        spdy_headers["version"] = "HTTP/1.1";

        {
            BalsaHeaders headers;
            memory_cache_->InsertFile(&headers, "GET_/path", "");
        }
        visitor->OnSynStream(stream_id, 0, 0, true, true, spdy_headers);
        ASSERT_TRUE(HasStream(stream_id));
    }

    TEST_P(SpdySMServerTest, NewStream)
    {
        uint32_t stream_id = 13;
        std::string filename = "foobar";

        {
            BalsaHeaders headers;
            memory_cache_->InsertFile(&headers, filename, "");
        }

        interface_->NewStream(stream_id, 0, filename);
        ASSERT_TRUE(HasStream(stream_id));
    }

    TEST_P(SpdySMServerTest, NewStreamError)
    {
        uint32_t stream_id = 82;
        const char* actual_data;
        size_t actual_size;
        testing::MockFunction<void(int)> checkpoint; // NOLINT

        interface_->NewStream(stream_id, 0, "nonexistingfile");

        ASSERT_EQ(2u, connection_->output_list()->size());

        {
            InSequence s;
            if (GetParam() < HTTP2) {
                EXPECT_CALL(*spdy_framer_visitor_, OnSynReplyMock(stream_id, false, _));
            } else {
                EXPECT_CALL(*spdy_framer_visitor_,
                    OnHeadersMock(stream_id, /*has_priority=*/false, _, _, _,
                        /*fin=*/false, _));
            }
            EXPECT_CALL(checkpoint, Call(0));
            EXPECT_CALL(*spdy_framer_visitor_,
                OnDataFrameHeader(stream_id, _, true));
            EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, _, _))
                .Times(1)
                .WillOnce(DoAll(SaveArg<1>(&actual_data), SaveArg<2>(&actual_size)));
            EXPECT_CALL(*spdy_framer_visitor_, OnStreamFrameData(stream_id, NULL, 0))
                .Times(1);
        }

        std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
        DataFrame* df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);
        checkpoint.Call(0);
        df = *i++;
        spdy_framer_->ProcessInput(df->data, df->size);

        ASSERT_EQ(2, spdy_framer_->frames_received());
        ASSERT_EQ(2u, spdy_framer_visitor_->actual_header_block_.size());
        ASSERT_EQ("404 Not Found",
            spdy_framer_visitor_->actual_header_block_["status"]);
        ASSERT_EQ("HTTP/1.1", spdy_framer_visitor_->actual_header_block_["version"]);
        ASSERT_EQ("wtf?", StringPiece(actual_data, actual_size));
    }

} // namespace

} // namespace net
